JSON Data in Flutter
JSON (JavaScript Object Notation) is one of the most commonly used data formats in Flutter applications. It is widely used for exchanging structured data between a Flutter app and a backend API. Flutter applications can decode JSON received from APIs, convert JSON into Dart objects, and encode Dart objects into JSON when sending data to a server.
1. What is JSON?
JSON stands for JavaScript Object Notation. It is a lightweight, text-based format used to represent structured data.
Although JSON originated from JavaScript, it is language-independent and is commonly used by APIs, mobile applications, web applications, and backend services.
Example of JSON
{
"id": 101,
"name": "Rahul",
"email": "[email protected]",
"age": 25
}
In this example, id, name, email, and age are JSON keys, while their associated values are the actual data.
2. Why is JSON Important in Flutter?
Flutter applications frequently communicate with REST APIs. Most APIs return data in JSON format, so Flutter developers need to understand how to read, process, create, and send JSON data.
- Fetching data from REST APIs
- Sending data to backend servers
- Displaying product information
- Handling login and registration responses
- Working with nested API responses
- Converting Dart objects into API request bodies
- Converting API responses into Dart model objects
3. JSON Data Types
JSON supports several basic data types.
| JSON Type |
Example |
Dart Equivalent |
| String |
"Flutter" |
String |
| Number |
25 |
int or double |
| Boolean |
true |
bool |
| Null |
null |
null |
| Object |
{"name":"Rahul"} |
Map |
| Array |
["Flutter","Dart"] |
List |
4. JSON Object
A JSON object contains key-value pairs enclosed in curly braces {}.
{
"name": "Amit",
"age": 28,
"isStudent": false
}
5. JSON Array
A JSON array contains multiple values and is represented using square brackets [].
[
"Flutter",
"Dart",
"Firebase",
"REST API"
]
An array can also contain multiple JSON objects.
[
{
"id": 1,
"name": "Rahul"
},
{
"id": 2,
"name": "Priya"
},
{
"id": 3,
"name": "Amit"
}
]
6. JSON and Dart
Flutter uses Dart, and Dart provides the dart:convert library for encoding and decoding JSON.
import 'dart:convert';
The two most important functions are:
jsonDecode() - Converts JSON text into Dart data.
jsonEncode() - Converts Dart data into JSON text.
7. Decoding JSON with jsonDecode()
Decoding means converting a JSON string into a Dart data structure.
import 'dart:convert';
const jsonString = '''
{
"name": "Rahul",
"age": 25
}
''';
final data = jsonDecode(jsonString);
print(data['name']);
print(data['age']);
Output
Rahul
25
Flutter's documentation describes manual JSON decoding using the built-in dart:convert library and jsonDecode(). :contentReference[oaicite:0]{index=0}
8. Understanding jsonDecode()
The result of jsonDecode() is dynamic, so the developer should convert it to an appropriate Dart type when working with structured data.
final Map data =
jsonDecode(jsonString) as Map;
print(data['name']);
9. Accessing JSON Values
const jsonString = '''
{
"id": 101,
"name": "Rahul",
"email": "[email protected]"
}
''';
final data = jsonDecode(jsonString)
as Map;
print(data['id']);
print(data['name']);
print(data['email']);
10. Converting JSON Arrays
When the JSON response contains an array, decode it into a Dart list.
const jsonString = '''
[
{
"id": 1,
"name": "Rahul"
},
{
"id": 2,
"name": "Priya"
}
]
''';
final List users = jsonDecode(jsonString);
for (final user in users) {
print(user['name']);
}
11. JSON to Dart Map
A JSON object can be represented in Dart as a Map.
final Map user = {
'id': 1,
'name': 'Rahul',
'age': 25,
};
12. JSON to Dart List
A JSON array can be represented as a Dart List.
final List skills = [
'Flutter',
'Dart',
'Firebase',
];
13. Encoding Dart Data into JSON
Encoding means converting Dart data into a JSON string.
import 'dart:convert';
final user = {
'id': 1,
'name': 'Rahul',
'email': '[email protected]',
};
final jsonString = jsonEncode(user);
print(jsonString);
Output
{"id":1,"name":"Rahul","email":"[email protected]"}
14. JSON Encoding a List
final skills = [
'Flutter',
'Dart',
'Firebase',
];
final jsonString = jsonEncode(skills);
print(jsonString);
Output
["Flutter","Dart","Firebase"]
15. JSON in HTTP API Responses
A common Flutter API flow looks like this:
Flutter Application
|
| HTTP Request
v
Backend API
|
| JSON Response
v
Flutter Application
|
| jsonDecode()
v
Dart Object / Model
|
v
Flutter UI
16. Fetching JSON from an API
The http package can be used to fetch JSON data from an API.
import 'dart:convert';
import 'package:http/http.dart' as http;
Future fetchData() async {
final response = await http.get(
Uri.parse(
'https://jsonplaceholder.typicode.com/albums/1',
),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body)
as Map;
print(data['title']);
} else {
throw Exception('Failed to load data');
}
}
Flutter's networking cookbook follows this general pattern: make the request, check the response status, decode the JSON body, and convert it into a Dart object. :contentReference[oaicite:1]{index=1}
17. Why Use Dart Model Classes?
Although accessing JSON directly through maps is convenient for small examples, model classes are usually easier to maintain as applications become larger.
Model classes provide:
- Centralized JSON conversion logic
- More maintainable API code
18. Creating a Dart Model
Suppose an API returns this JSON:
{
"id": 1,
"name": "Rahul",
"email": "[email protected]"
}
Create a corresponding Dart class:
class User {
final int id;
final String name;
final String email;
const User({
required this.id,
required this.name,
required this.email,
});
}
19. Adding fromJson()
The fromJson() factory constructor converts a JSON map into a Dart object.
class User {
final int id;
final String name;
final String email;
const User({
required this.id,
required this.name,
required this.email,
});
factory User.fromJson(Map json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
);
}
}
Flutter's JSON documentation recommends model classes with fromJson() and toJson() for stronger type safety and cleaner serialization logic. :contentReference[oaicite:2]{index=2}
20. Using fromJson()
const jsonString = '''
{
"id": 1,
"name": "Rahul",
"email": "[email protected]"
}
''';
final Map data =
jsonDecode(jsonString);
final user = User.fromJson(data);
print(user.name);
print(user.email);
21. Adding toJson()
The toJson() method converts a Dart object into a map that can be encoded into JSON.
class User {
final int id;
final String name;
final String email;
const User({
required this.id,
required this.name,
required this.email,
});
factory User.fromJson(Map json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
);
}
Map toJson() => {
'id': id,
'name': name,
'email': email,
};
}
22. Encoding a Model Object
final user = User(
id: 1,
name: 'Rahul',
email: '[email protected]',
);
final jsonString = jsonEncode(user.toJson());
print(jsonString);
23. Complete JSON Model Example
import 'dart:convert';
class Product {
final int id;
final String name;
final double price;
final bool available;
const Product({
required this.id,
required this.name,
required this.price,
required this.available,
});
factory Product.fromJson(Map json) {
return Product(
id: json['id'] as int,
name: json['name'] as String,
price: (json['price'] as num).toDouble(),
available: json['available'] as bool,
);
}
Map toJson() {
return {
'id': id,
'name': name,
'price': price,
'available': available,
};
}
}
void main() {
const jsonString = '''
{
"id": 1,
"name": "Flutter Course",
"price": 4999.0,
"available": true
}
''';
final data = jsonDecode(jsonString)
as Map;
final product = Product.fromJson(data);
print(product.name);
print(product.price);
final jsonOutput = jsonEncode(product.toJson());
print(jsonOutput);
}
24. JSON Arrays and Model Lists
When an API returns multiple objects, convert each JSON object into a model object.
final List data = jsonDecode(response.body);
final List products = data
.map(
(item) => Product.fromJson(
item as Map,
),
)
.toList();
25. Displaying JSON Data in ListView
ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return ListTile(
title: Text(product.name),
subtitle: Text('₹${product.price}'),
trailing: product.available
? const Icon(Icons.check)
: const Icon(Icons.close),
);
},
)
26. Nested JSON Objects
Real-world APIs often contain nested JSON objects.
{
"id": 1,
"name": "Rahul",
"address": {
"street": "MG Road",
"city": "Mumbai",
"zipCode": "400001"
}
}
27. Model for Nested JSON
class Address {
final String street;
final String city;
final String zipCode;
const Address({
required this.street,
required this.city,
required this.zipCode,
});
factory Address.fromJson(Map json) {
return Address(
street: json['street'] as String,
city: json['city'] as String,
zipCode: json['zipCode'] as String,
);
}
Map toJson() => {
'street': street,
'city': city,
'zipCode': zipCode,
};
}
28. User Model with Nested Address
class User {
final int id;
final String name;
final Address address;
const User({
required this.id,
required this.name,
required this.address,
});
factory User.fromJson(Map json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
address: Address.fromJson(
json['address'] as Map,
),
);
}
Map toJson() => {
'id': id,
'name': name,
'address': address.toJson(),
};
}
29. Nested JSON Arrays
JSON can contain arrays inside objects.
{
"id": 1,
"name": "Rahul",
"skills": [
"Flutter",
"Dart",
"Firebase"
]
}
Dart Model
class User {
final int id;
final String name;
final List skills;
const User({
required this.id,
required this.name,
required this.skills,
});
factory User.fromJson(Map json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
skills: List.from(
json['skills'] as List,
),
);
}
}
30. Nullable JSON Fields
API responses may contain null values. Dart's null safety should be considered when defining models.
{
"id": 1,
"name": "Rahul",
"phone": null
}
Dart Model
class User {
final int id;
final String name;
final String? phone;
const User({
required this.id,
required this.name,
this.phone,
});
factory User.fromJson(Map json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
phone: json['phone'] as String?,
);
}
}
31. Providing Default Values
Sometimes an API field may be missing. A default value can be used when appropriate.
class User {
final String name;
final String role;
const User({
required this.name,
required this.role,
});
factory User.fromJson(Map json) {
return User(
name: json['name'] as String? ?? 'Unknown',
role: json['role'] as String? ?? 'User',
);
}
}
32. Handling Numbers in JSON
JSON numbers may represent integers or floating-point values. Using num can be useful when the API may return either representation.
final price = (json['price'] as num).toDouble();
This is useful when an API may return values such as 4999 or 4999.50.
33. Handling Boolean Values
{
"name": "Flutter Course",
"isActive": true
}
final isActive = json['isActive'] as bool;
34. Handling Date Strings
JSON does not have a dedicated DateTime type. APIs commonly send dates as strings.
{
"createdAt": "2026-09-19T10:30:00Z"
}
Convert the value to a Dart DateTime:
final createdAt = DateTime.parse(
json['createdAt'] as String,
);
35. JSON Parsing Errors
JSON parsing can fail when the response is malformed or has an unexpected structure.
try {
final data = jsonDecode(response.body);
print(data);
} catch (e) {
print('Invalid JSON: $e');
}
36. Handling Unexpected JSON Types
Always be careful about the data types returned by an API.
final age = json['age'];
if (age is int) {
print('Age: $age');
} else {
print('Invalid age value');
}
37. JSON and FutureBuilder
FutureBuilder can be used to display JSON data after an asynchronous API request completes.
FutureBuilder(
future: fetchProduct(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
if (!snapshot.hasData) {
return const Text('No product found');
}
final product = snapshot.data!;
return Column(
children: [
Text(product.name),
Text('₹${product.price}'),
],
);
},
)
38. JSON and POST Requests
JSON is not only used for API responses. It is also commonly used as the request body when sending data to an API.
import 'dart:convert';
import 'package:http/http.dart' as http;
Future createUser() async {
final user = {
'name': 'Rahul',
'email': '[email protected]',
};
final response = await http.post(
Uri.parse('https://example.com/api/users'),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode(user),
);
print(response.statusCode);
}
Flutter's networking documentation uses jsonEncode() to convert Dart data into a JSON request body for POST operations. :contentReference[oaicite:3]{index=3}
39. JSON and API Error Responses
APIs may return errors in JSON format.
{
"success": false,
"message": "Invalid email address",
"errors": {
"email": "Please enter a valid email"
}
}
Reading the Error
final data = jsonDecode(response.body)
as Map;
final message = data['message'] as String?;
print(message);
40. Creating an API Response Model
For complex APIs, it can be useful to create a separate model for API response information.
class ApiResponse {
final bool success;
final String message;
const ApiResponse({
required this.success,
required this.message,
});
factory ApiResponse.fromJson(
Map json,
) {
return ApiResponse(
success: json['success'] as bool? ?? false,
message: json['message'] as String? ?? '',
);
}
}
41. JSON Serialization vs Deserialization
| Operation |
Description |
Common Function |
| Decoding |
JSON string → Dart data |
jsonDecode() |
| Encoding |
Dart data → JSON string |
jsonEncode() |
| Deserialization |
JSON data → Dart model object |
fromJson() |
| Serialization |
Dart model object → JSON-compatible data |
toJson() |
42. Manual JSON Serialization
Manual serialization means writing fromJson() and toJson() methods yourself.
class Product {
final int id;
final String name;
const Product({
required this.id,
required this.name,
});
factory Product.fromJson(Map json) {
return Product(
id: json['id'] as int,
name: json['name'] as String,
);
}
Map toJson() => {
'id': id,
'name': name,
};
}
Manual serialization is simple and useful for smaller projects or prototypes. Flutter's official documentation describes this approach using dart:convert. :contentReference[oaicite:4]{index=4}
43. JSON Serialization with Code Generation
For medium and large applications with many models, manually writing JSON serialization code can become repetitive. Flutter's documentation discusses code-generation approaches such as json_serializable for larger projects. :contentReference[oaicite:5]{index=5}
Install json_serializable
flutter pub add json_annotation
flutter pub add --dev build_runner
flutter pub add --dev json_serializable
44. Using json_serializable
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
final String name;
final String email;
User({
required this.name,
required this.email,
});
factory User.fromJson(
Map json,
) => _$UserFromJson(json);
Map toJson() => _$UserToJson(this);
}
45. Running Code Generation
After creating a json_serializable model, generate the serialization code using:
dart run build_runner build --delete-conflicting-outputs
For continuous generation during development:
dart run build_runner watch --delete-conflicting-outputs
Flutter's JSON documentation provides these commands for generating the .g.dart serialization files. :contentReference[oaicite:6]{index=6}
46. Mapping Different JSON Key Names
Sometimes an API uses a different naming convention from your Dart model.
For example, an API may return:
{
"first_name": "Rahul"
}
With json_serializable, @JsonKey can map the JSON key to a Dart property.
@JsonKey(name: 'first_name')
final String firstName;
47. Nested Models with Code Generation
Large APIs can contain multiple nested objects. Each nested object can have its own model class.
class Address {
final String city;
Address({
required this.city,
});
factory Address.fromJson(
Map json,
) => Address(
city: json['city'] as String,
);
Map toJson() => {
'city': city,
};
}
48. JSON Data and Flutter UI
JSON data can be displayed using standard Flutter widgets.
Card(
child: ListTile(
title: Text(product.name),
subtitle: Text('₹${product.price}'),
trailing: Text(
product.available ? 'Available' : 'Unavailable',
),
),
)
49. JSON Data Flow in a Flutter Application
API Server
↓
JSON Response
↓
http.Response
↓
response.body
↓
jsonDecode()
↓
Map / List
↓
Model.fromJson()
↓
Dart Object
↓
Application State
↓
Flutter Widget
↓
User Interface
50. Complete JSON + API Example
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class Product {
final int id;
final String name;
final double price;
const Product({
required this.id,
required this.name,
required this.price,
});
factory Product.fromJson(
Map json,
) {
return Product(
id: json['id'] as int,
name: json['name'] as String,
price: (json['price'] as num).toDouble(),
);
}
}
Future> fetchProducts() async {
final response = await http.get(
Uri.parse('https://example.com/api/products'),
headers: {
'Accept': 'application/json',
},
);
if (response.statusCode != 200) {
throw Exception('Failed to load products');
}
final List data =
jsonDecode(response.body);
return data
.map(
(item) => Product.fromJson(
item as Map,
),
)
.toList();
}
class ProductScreen extends StatefulWidget {
const ProductScreen({super.key});
@override
State createState() =>
_ProductScreenState();
}
class _ProductScreenState
extends State {
late Future> productsFuture;
@override
void initState() {
super.initState();
productsFuture = fetchProducts();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Products'),
),
body: FutureBuilder>(
future: productsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
return Center(
child: Text('Error: ${snapshot.error}'),
);
}
final products = snapshot.data ?? [];
if (products.isEmpty) {
return const Center(
child: Text('No products found'),
);
}
return ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return ListTile(
title: Text(product.name),
subtitle: Text(
'₹${product.price}',
),
);
},
);
},
),
);
}
}
51. Parsing Large JSON Data
Large JSON responses can require significant processing. For very large datasets, expensive JSON parsing can be moved to another isolate so the user interface remains responsive.
import 'dart:convert';
import 'package:flutter/foundation.dart';
List> parseJson(
String responseBody,
) {
final data = jsonDecode(responseBody) as List;
return data
.map(
(item) => item as Map,
)
.toList();
}
final result = await compute(
parseJson,
response.body,
);
Flutter's networking documentation demonstrates using compute() to move JSON parsing work to another isolate when appropriate for large responses. :contentReference[oaicite:7]{index=7}
52. JSON Best Practices
- Use
jsonDecode() to decode JSON strings.
- Use
jsonEncode() to encode Dart data.
- Use model classes for structured API data.
- Use
fromJson() to create model objects.
- Use
toJson() to convert models into JSON-compatible maps.
- Handle nullable fields carefully.
- Validate important data types.
- Handle malformed or unexpected JSON responses.
- Check HTTP status codes before parsing successful responses.
- Keep JSON parsing logic outside UI widgets when possible.
- Use code generation for larger projects when it reduces repetitive serialization code.
- Use background parsing for very large JSON responses when necessary.
- Write tests for important serialization and deserialization logic.
53. Common JSON Mistakes
- Forgetting to import
dart:convert.
- Using
jsonDecode() without checking the response.
- Assuming every JSON field has the expected type.
- Forgetting to convert nested JSON objects into their models.
- Trying to access a list as if it were a map.
- Trying to access a map as if it were a list.
- Not handling malformed JSON.
- Putting all JSON parsing logic inside UI widgets.
- Creating large amounts of repetitive manual serialization code in a large application.
54. JSON Map vs List
| JSON Structure |
Dart Structure |
Example |
| Object |
Map |
User information |
| Array |
List |
List of users |
| Nested Object |
Nested Map or Model |
User address |
| Nested Array |
List inside Map/Model |
User skills |
55. Practice Exercise
Create a Flutter application that consumes a product API.
- Fetch a JSON list of products.
- Create a
Product model.
- Implement
Product.fromJson().
- Convert the JSON array into
List.
- Display the products using
ListView.builder.
- Show a loading indicator while the API request is running.
- Show an error message if JSON parsing fails.
- Handle an empty product list.
- Add a product search feature.
- Add a POST request to create a new product.
- Implement
toJson() for the product model.
56. Interview Questions
- What is JSON?
- Why is JSON commonly used in Flutter applications?
- What is the difference between JSON encoding and decoding?
- What is
jsonDecode()?
- What is
jsonEncode()?
- Which Dart library provides JSON encoding and decoding?
- How do you convert JSON into a Dart object?
- What is the purpose of
fromJson()?
- What is the purpose of
toJson()?
- How do you parse a JSON array?
- How do you handle nested JSON?
- How do you handle nullable JSON fields?
- Why are model classes useful for JSON parsing?
- What is manual JSON serialization?
- What is
json_serializable?
- When should code generation be considered for JSON serialization?
- How can large JSON responses be parsed without blocking the UI?
- How do you convert an API response into a list of Dart objects?
- How do you send JSON data using a POST request?
- How do you handle invalid JSON?
57. Quick Revision
| Concept |
Key Point |
| JSON |
Lightweight format for structured data exchange |
dart:convert |
Dart library containing JSON encoding and decoding utilities |
jsonDecode() |
Converts JSON text into Dart data |
jsonEncode() |
Converts Dart data into JSON text |
fromJson() |
Creates a Dart model from JSON data |
toJson() |
Converts a Dart model into JSON-compatible data |
| Map |
Represents a JSON object |
| List |
Represents a JSON array |
| Model Class |
Provides structured representation of API data |
| json_serializable |
Generates JSON serialization code |
compute() |
Can move expensive parsing work to another isolate |
58. Official Flutter Resources
59. Learn Flutter with JustAcademy
To learn Flutter development through structured training, visit the JustAcademy Flutter Training Course.
To register for a Flutter course demonstration, visit the JustAcademy Flutter Course Demo Registration page.
Conclusion
JSON is a fundamental part of Flutter application development because it provides a standard way to exchange structured data between Flutter applications and backend services. Developers should understand JSON objects, arrays, data types, jsonDecode(), jsonEncode(), model classes, fromJson(), toJson(), nested JSON, nullable values, error handling, and JSON serialization.
For small projects, manual serialization with dart:convert can be straightforward. For medium and large applications, code-generation approaches such as json_serializable can reduce repetitive serialization code. :contentReference[oaicite:8]{index=8}
The basic JSON workflow can be remembered as:
API Response
↓
JSON String
↓
jsonDecode()
↓
Map / List
↓
fromJson()
↓
Dart Model
↓
Application State
↓
Flutter UI